﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using RoofusUIControls;
using RootfusCoreComponents;
using RootfusCoreComponents.IO;

namespace RoofusUIControls
{
    /// <summary>
    /// Interaction logic for RootfusProgrammingSurface.xaml
    /// </summary>
    public partial class RootfusProgrammingSurface : UserControl
    {
        public RootfusProgrammingSurface()
        {
            InitializeComponent();
            dragSurface.ScrollContainer = scroller; // TODO: Why can't we do this in xaml?
        }

        /// <summary>
        /// Keep track of the default place to insert a new item. Make a "guess".
        /// </summary>
        Point _default_item_add_position = new Point(10, 10);

        /// <summary>
        /// Someone is asking us to add an item to the surface, but they don't seem to care where they
        /// do it. Use the default position.
        /// </summary>
        /// <param name="rootfusSurfaceItem"></param>
        public void AddItem(RootfusSurfaceItemWrapper rootfusSurfaceItem)
        {
            AddItem(rootfusSurfaceItem, _default_item_add_position);
            _default_item_add_position.X += 100;
        }

        /// <summary>
        /// True if the surface has been modified in some way. Note that no one in here ever resets this, we only set
        /// it!
        /// </summary>
        public bool Modified
        {
            get { return (bool)GetValue(ModifiedProperty); }
            set { SetValue(ModifiedProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Modified.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ModifiedProperty =
            DependencyProperty.Register("Modified", typeof(bool), typeof(RootfusProgrammingSurface), new UIPropertyMetadata(false));

        /// <summary>
        /// Add the item at a particular point on our design surface. So what if it overlaps. :-)
        /// </summary>
        /// <param name="rootfusSurfaceItem"></param>
        /// <param name="_default_item_add_position"></param>
        public SurfaceItem AddItem(RootfusSurfaceItemWrapper rootfusSurfaceItem, Point pos)
        {
            ///
            /// Create the GUI wrapper and position it in the default spot.
            /// 

            SurfaceItem item = new SurfaceItem(rootfusSurfaceItem);
            DragCanvas.SetTop(item, pos.Y);
            DragCanvas.SetLeft(item, pos.X);
            DragCanvas.SetZIndex(item, 10);

            ///
            /// Let the guys know we can drag by grabbing hold of this thing
            ///

            DragCanvas.SetCanUseAsDragPad(item.DragHandle, true);

            ///
            /// Where are the visual connectors going to go?
            ///

            DragConnectorCanvas.SetIsSink(item.rectInputPins, true);
            DragConnectorCanvas.SetIsSource(item.rectOutputPins, true);

            ///
            /// Setup what object it is that we want to return when the drag connector guy does its signal.
            ///

            DragConnectorCanvas.SetSourceSinkObject(item.rectInputPins, item);
            DragConnectorCanvas.SetSourceSinkObject(item.rectOutputPins, item);

            ///
            /// When the user modifies this guy, we should know about it.
            /// 

            item.ItemChanged += new SurfaceItem.ItemChangedCallback(item_ItemChanged);
            item.ItemClosing += new SurfaceItem.ItemClosingCallback(item_ItemClosing);

            ///
            /// Add it to teh list of children the drag surface is going to manage - also so it displays! :-)
            ///

            dragSurface.Children.Add(item);
            Modified = true;

            return item;
        }

        /// <summary>
        /// The item is going away. Clean up!
        /// </summary>
        /// <param name="item"></param>
        void item_ItemClosing(SurfaceItem item)
        {
            ///
            /// Get us off the surface
            /// 

            dragSurface.Children.Remove(item);

            ///
            /// Unhook the events.
            ///

            item.ItemChanged -= new SurfaceItem.ItemChangedCallback(item_ItemChanged);
            item.ItemClosing -= new SurfaceItem.ItemClosingCallback(item_ItemClosing);

            ///
            /// Now, if this item is being used by anyone in a connection, then it had better be
            /// nulled out!
            /// 

            List<SurfaceItem> surface_items = new List<SurfaceItem>();
            foreach (var c in dragSurface.Children.OfType<SurfaceItem>())
            {
               surface_items.Add(c as SurfaceItem);
            }
            foreach (var c in surface_items)
            {
                c.RemoveReferencesTo(item);
            }

            ///
            /// Finally, if there are any connectors hanging around we should get rid of them!
            /// 

            var bad_connectors = from c in _connectors
                                 where c.Sink == null
                                 select c;
            foreach (var citem in bad_connectors.ToArray())
            {
                _connectors.Remove(citem);
            }

            ///
            /// We are so modified!
            ///

            Modified = true;
        }

        /// <summary>
        /// The item has changed...
        /// </summary>
        void item_ItemChanged()
        {
            Modified = true;
        }

        private List<SurfaceItemConnector> _connectors = new List<SurfaceItemConnector>();

        /// <summary>
        /// Called when the user connects together two surface items. We draw the link connecting them
        /// and then put up the dialog box that is the switchboard for this sink object.
        /// </summary>
        /// <param name="obj_source"></param>
        /// <param name="obj_sink"></param>
        private void ConnectSurfaceItems(object obj_source, object obj_sink, bool show_connection_dialog)
        {
            SurfaceItem r_source = (SurfaceItem)obj_source;
            SurfaceItem r_sink = (SurfaceItem)obj_sink;

            /// Create the object to manage this particular connection.
            SurfaceItemConnector con = new SurfaceItemConnector();
            con.Sink = r_sink;
            con.Source = r_source;
            con.Host = this;

            _connectors.Add(con);

            /// Display the sink's connection guy
            if (show_connection_dialog)
            {
                r_sink.ShowConfigureInputDialog();
            }
        }
        private void ConnectSurfaceItems(object obj_source, object obj_sink)
        {
            ConnectSurfaceItems(obj_source, obj_sink, true);
        }

        /// <summary>
        /// User wants to start fresh. Just erase everything. No questions asked. :-)
        /// </summary>
        public void RemoveAllSIItems()
        {
            ///
            /// So when they do the next insert we start correctly!
            /// 

            _default_item_add_position = new Point(10, 10);

            ///
            /// Now, clear everything out. Since this is all garbage collected,
            /// we don't have to worry too much about the order...
            /// 

            _connectors.Clear();
            dragSurface.Children.Clear();
        }

        /// <summary>
        /// When we get here -- we need to save everything to a file. Cool!
        /// </summary>
        /// <param name="_save_filename"></param>
        public void SaveToFile(string save_filename)
        {
            ItemFileOperator io = new ItemFileOperator(save_filename);

            foreach (var i in dragSurface.Children.OfType<SurfaceItem>())
            {
                io.AddSurfaceItem(i.Wrapper, new SIUIInfo(Canvas.GetTop(i), Canvas.GetLeft(i), i.ItemSize.Width, i.ItemSize.Height));
            }

            _connectors.ForEach(delegate(SurfaceItemConnector c)
            {
                io.AddSIGUIConnection(c.Source.Wrapper, c.Sink.Wrapper);
            });

            io.Save();
        }

        /// <summary>
        /// Given the names of two surface items, connect them together.
        /// </summary>
        /// <param name="source_name"></param>
        /// <param name="sink_name"></param>
        public void AddGUIItemConnection(string source_name, string sink_name)
        {
            SurfaceItem source = FindSurfaceItemByName(source_name);
            SurfaceItem sink = FindSurfaceItemByName(sink_name);
            if (source != null && sink != null)
            {
                ConnectSurfaceItems(source, sink, false);
            }
        }

        /// <summary>
        /// Lookup a surface item by finding its name.
        /// </summary>
        /// <param name="source_name"></param>
        /// <returns></returns>
        private SurfaceItem FindSurfaceItemByName(string source_name)
        {
            SurfaceItem source = null;
            foreach (var c in dragSurface.Children)
            {
                if (c.GetType() == typeof(SurfaceItem))
                {
                    SurfaceItem si = c as SurfaceItem;
                    if (si.Wrapper.UserName == source_name)
                    {
                        source = si;
                        break;
                    }
                }
            }
            return source;
        }
    }
}
